package cn.iocoder.yudao.module.infra.framework.file.core.client.chevereto;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileContentMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileMediaUtils;
import org.springframework.cache.annotation.Cacheable;

import java.net.URL;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;


/**
 * 基于 DB 存储的文件客户端的配置类
 *
 * @author 芋道源码
 */
public class CheveretoFileClient extends AbstractFileClient<CheveretoFileClientConfig> {

    /**
     * 默认压缩媒体文件的大小上限（单位：字节）
     * 超过这个大小的媒体文件会被压缩，1MB
     */
    public static final int DEFAULT_COMPRESS_SIZE_THRESHOLD = 1024 * 1024;
    private static final String INFO_API = "/api/1/image";
    private static final int HTTP_TIMEOUT = 5000;
    
    // MD5 缓存，避免重复上传
    private static final Map<String, String> MD5_URL_CACHE = new ConcurrentHashMap<>();

    public CheveretoFileClient(Long id, CheveretoFileClientConfig config) {
        super(id, config);
        this.fileContentMapper = SpringUtil.getBean(FileContentMapper.class);
        this.fileMapper = SpringUtil.getBean(FileMapper.class);
    }

    private FileContentMapper fileContentMapper;

    private FileMapper fileMapper;


    @Override
    protected void doInit() {
        fileContentMapper = SpringUtil.getBean(FileContentMapper.class);
        fileMapper = SpringUtil.getBean(FileMapper.class);
    }

    @Override
    public String upload(byte[] content, String path, String type) {
        // 媒体文件压缩
        if (content.length > DEFAULT_COMPRESS_SIZE_THRESHOLD) {
            // 对图片文件特殊处理，确保压缩到阈值以下
            content = FileMediaUtils.smartCompressMedia(content, path);
            // 二次检查压缩结果，确保文件大小小于阈值
            if (content.length > DEFAULT_COMPRESS_SIZE_THRESHOLD) {
                content = FileMediaUtils.smartCompressMedia(content, path);
            }
        }
        
        try {
            // 计算 MD5，检查缓存
            String md5 = DigestUtil.md5Hex(content);
            String cachedUrl = MD5_URL_CACHE.get(md5);
            if (cachedUrl != null) {
                return cachedUrl;
            }
            
            // 检查数据库是否已有相同 MD5
            FileDO existingFile = fileMapper.selectOne(FileDO::getMd5, md5);
            if (existingFile != null) {
                MD5_URL_CACHE.put(md5, existingFile.getUrl());
                return existingFile.getUrl();
            }

            String apiUrl = config.getUrl() + "?key=" + config.getKey() + "&format=json";
            try {
                JSONObject json;
                try (
                        HttpResponse response = HttpRequest.post(apiUrl)
                        .timeout(HTTP_TIMEOUT)
                        .form("source", content, path)
                        .execute();
                ) {
                    json = JSONUtil.parseObj(response.body());
                }

                if (json.getInt("status_code", 500) != 200) {
                    throw exception(new ErrorCode(999999999,"API 错误: " + json.getStr("error")));
                }

                JSONObject image = json.getJSONObject("image");
                String displayUrl = config.getDomain() + new URL(image.getStr("display_url")).getPath();

                // 缓存结果
                MD5_URL_CACHE.put(md5, displayUrl);

                // 异步保存元数据
                saveMetadata(image, content.length, path, type, displayUrl, md5);

                return displayUrl;
            } catch (Exception e) {
                throw new RuntimeException("上传失败: " + e.getMessage(), e);
            }
        } catch (Exception e) {
            throw exception(new ErrorCode(999999999,"上传失败: " + e.getMessage()));
        }
    }

    protected void saveMetadata(JSONObject image, int contentLength, String path,
                                String type, String url, String md5) {
        FileDO file = new FileDO()
                .setConfigId(this.getId())
                .setName(image.getStr("name"))
                .setPath(path)
                .setUrl(url)
                .setType(type)
                .setSize(contentLength)
                .setMd5(md5);
        fileMapper.insert(file);
        fileContentMapper.insert(new FileContentDO()
                .setConfigId(getId())
                .setPath(path)
                .setContent(null));
    }

    /**
     * 根据 MD5 查找文件
     * 
     * @param md5 文件的 MD5 值
     * @return 文件的访问 URL，不存在则返回 null
     */
    @Cacheable(cacheNames = "chevereto:md5", key = "#md5")
    public String getFileUrlByMD5(String md5) {
        // 先查缓存
        String cachedUrl = MD5_URL_CACHE.get(md5);
        if (cachedUrl != null) {
            return cachedUrl;
        }
        
        // 查数据库
        FileDO file = fileMapper.selectOne(FileDO::getMd5, md5);
        if (file != null) {
            // 更新缓存
            MD5_URL_CACHE.put(md5, file.getUrl());
            return file.getUrl();
        }
        return null;
    }

    /**
     * 优化点 4：带缓存的查询
     */
    @Override
    public byte[] getContent(String path) {
        List<FileContentDO> list = fileContentMapper.selectListByConfigIdAndPath(getId(), path);
        if (CollUtil.isEmpty(list)) {
            return null;
        }
        // 排序后，拿 id 最大的，即最后上传的
        list.sort(Comparator.comparing(FileContentDO::getId));
        return CollUtil.getLast(list).getContent();
    }

    @Override
    public void delete(String path) {
        // 异步删除优化
        ThreadUtil.execute(() -> {
            // 先删除对应的缓存
            List<FileDO> files = fileMapper.selectListByConfigIdAndPath(getId(), path);
            if (!CollUtil.isEmpty(files)) {
                for (FileDO file : files) {
                    MD5_URL_CACHE.remove(file.getMd5());
                }
            }
            
            fileContentMapper.deleteByConfigIdAndPath(getId(), path);
            fileMapper.deleteByConfigIdAndPath(getId(), path);
        });
    }

    /**
     * 获取图片信息
     * 
     * @param shortCode 图片短码
     * @return 图片信息
     */
    public JSONObject getImageInfo(String shortCode) {
        String apiUrl = config.getUrl() + INFO_API + "?key=" + config.getKey() + "&short=" + shortCode;
        try (HttpResponse response = HttpRequest.get(apiUrl)
                .timeout(HTTP_TIMEOUT)
                .execute()) {
            JSONObject json = JSONUtil.parseObj(response.body());
            if (json.getInt("status_code", 500) != 200) {
                throw exception(new ErrorCode(999999999,"API 错误: " + json.getStr("error")));
            }
            return json.getJSONObject("image");
        } catch (Exception e) {
            throw exception(new ErrorCode(999999999,"获取图片信息失败: " + e.getMessage()));
        }
    }

}
